#include "decoder.h"
#include "bit_reader.h"
#include "defs.h"

#define MAX_CODE 80000
#define END (1 << 30)

int counter = 0;
entry dict[MAX_CODE];
entry special257;



//
// Main function, program entry.
// RETURNS
//   0 - success    
//
void
decode(const char *inputfile, const char *outputfile)
{
    bitstream stream;
    init_buffer(&stream);


    // Initialization
    FILE* infile = fopen(inputfile, "rb");
    FILE* outfile = fopen(outputfile, "wb");

    int i = 0;
    int byte = 0;
    int currcode = 0;
    int prevcode = 0;
    char firstchar;
    int start = 1;
    int width = 0;
    entry item;
    init_dict();
    int special = 0;
    int debug_counter = 0;
    //printf("%d\n", special257.length);
    //return 0;

    // Start decoding
    width = 9; // TODO: Boundary situation error?
    prevcode = read_buffer(&stream, width, infile);
    

    item = get_item(prevcode);
    print(item, outfile);

    /*
    for(i = 0; i < item.length; i++){
        printf("%p ", item.ptr[i]);
    }
    printf(" cw: %d\t\twidth: %d\tdictsize: %d\n", prevcode, width, 0);
    */
    
    while(1){
        width = size_to_width(counter + 1); // TODO: Boundary situation error?
        currcode = read_buffer(&stream, width, infile);

        //printf("%d %d %d\n", currcode, width, counter);
        //printf("cw: %d\t\twidth: %d\tdictsize: %d\n", currcode, width, counter+1);

        if(currcode == 0){
            break;
        }

        if(currcode == 1){
            init_dict();
            prevcode = currcode;
            counter -= 1;
            continue;
        }

        if(currcode == counter && currcode != 257){
            special = 1;
        } else {
            special = 0;
        }

        //printf("1---\n");
        item = get_entry(prevcode, currcode);

        /*
        for(i = 0; i < item.length; i++){
            printf("%p ", item.ptr[i]);
        }
        printf(" cw: %d\t\twidth: %d\tdictsize: %d\n", currcode, width, 0);
        */


        //printf("1.1--\n");
        print(item, outfile);
        //printf("1.2--\n");
        //printf("%d, %d\n", prevcode, currcode);
        firstchar = item.ptr[0];
        //printf("2---\n");
        if(! special)
            add_dict(prevcode, firstchar);
        prevcode = currcode;

    }

    return;

}



//
// Wrapper for output
void
print(entry item, FILE* outfile){
    fwrite(item.ptr, sizeof(unsigned char), item.length, outfile );
}


//
// Dictionary initialization
//
void
init_dict(){

    int i;
    counter = 2;
    for(i = 0; i < 256; i++){
        dict[i+2].ptr = (unsigned char*)malloc(1 * sizeof(unsigned char));
        dict[i+2].ptr[0] = (unsigned char) i;
        dict[i+2].length = 1;
        counter += 1;
    }

    special257.length = 1;
    special257.ptr = (unsigned char * ) malloc(1 * sizeof(unsigned char));
    special257.ptr[0] = 0xff;

}

//
// Helper function for printing dictionary status
//
void
show_dict(){
    int i, j;
    printf("debug..\n");
    for( i = 0; i < counter; i++){
        printf("#%d\n", i);
        for(j = 0; j < dict[i].length; j++){
            printf("x%x ", dict[i].ptr[j]);    
        }
        printf("\n");
        
    }
}

//
// Function for adding entry
// Parameter:
//  old - The prefix of the new item 
//  c   - The last char of the new item
//
void
add_dict(int prevcode, unsigned char c){

    //printf("Counter: %d\n", counter);
    if (counter >= DICT_SIZE){
        //printf("Counter Boundary: %d\n", counter);
        return;
    }

    int old_len = get_item(prevcode).length;
    dict[counter].ptr = (unsigned char*)malloc(old_len + 1);
    memcpy(dict[counter].ptr, get_item(prevcode).ptr, old_len);
    dict[counter].ptr[old_len] = c;
    dict[counter].length = old_len + 1;
    counter += 1;
}

//
// Function for adding entry
// Parameter:
//  prevcode - The previous code in the decoding
//  currcode - The index/code to look up in the dictionary
//
entry
get_entry(int prevcode, int currcode){
    if(currcode == 257){
        return special257;
    }
    if(currcode < counter){
        return dict[currcode];
    } else {
        if (prevcode != 257)
            add_dict(prevcode, dict[prevcode].ptr[0]);
        else
            add_dict(prevcode, special257.ptr[0]);

        return dict[currcode];
    }
}

//
// Wrapper for printing into the standard error
// Parameter:
//  err - Error message
//
void
print_error(char* err){
    fprintf(stderr,"%s\n", err);
}

entry
get_item(int index){
    if(index == 257)
        return special257;

    else
        return dict[index];
}